iT邦幫忙

2024 iThome 鐵人賽

DAY 23
0
Modern Web

Svelte 的奇妙冒險系列 第 23

[Svelte 的奇妙冒險] Day 23 - Hooks

  • 分享至 

  • xImage
  •  

SvelteKit 提供了一系列 function 讓我們可以攔截特定事件並且可以對這些事件做出不同的回應,也就是類似 middleware 的概念。

主要分為 hooks.server.tshooks.client.tshooks.ts 看名字應該真的他們執行的地方在哪裡了,只是跟 page 或者 layout 不一樣的地方是 hooks 多了一個只有 hooks.client.ts

因為我個人覺得會運用到 hooks.server.ts 場景多了一點,所以接下來主要會聚焦在 hooks.server.ts 上。

handle

基本用法

當我們向 SvelteKit 的 server 發出任何請求時都會被 handle function 所攔截到,不管是打+server.ts 的 API 還是 +(page|layout).server.tsload 只要是跟 server 有關的請求都會被它攔截。

按照直覺我覺得應該是也要把 +page.ts 的 server-side 的執行也攔截住才對,但實際上並沒有

// in src/hooks.server.ts

import { type Handle } from '@sveltejs/kit';

export const handle: Handle = async ({ event, resolve }) => {
	console.log(event.request.url);

	return await resolve(event);
};

SvelteKit 預設是要把 kit 放在 src/

然後新增 day23/+page.server.ts

import type { PageServerLoad } from './$types';

export const load: PageServerLoad = async () => {
	console.log('day23/+page.server.ts load');
};

會發現我們只要有訪問/預載到 +page.server.tsload 的路由或者有去執行 +server.ts 的 API 還有第一次進入網站/重新整理的那次 SSR 都會被 handle 所攔截到。

簡單的小範例

那它有什麼用?既然上述場景都能夠觸發 handle function 那就很適合來當作登入驗證的 middleware 了吧,那接下來我們就來寫個「最最最最陽春」的登入驗證機制。

首先來實作登入的 API

// in day23/auth/+server.ts
import { error } from '@sveltejs/kit';
import type { RequestHandler } from './$types';

export const POST: RequestHandler = async ({ request, cookies }) => {
	const { email, password } = await request.json();
	if (email === 'todd@example.com' && password === '123456') {
		cookies.set('session', email, {
			path: '/',
			httpOnly: true,
			maxAge: 60 * 60 * 24
		});

		return new Response(JSON.stringify({ success: true }));
	}

	error(401, 'Invalid email or password');
};

那因為 SvelteKit 的 API 路由本身就提供 cookies 這個類似 context 的寫法,所以就直接使用 cookies.set 而不是放在 header 裡。

然後將 hooks.server.ts 改為

// in src/hooks.server.ts

import { error, type Handle } from '@sveltejs/kit';

export const handle: Handle = async ({ event, resolve }) => {
	console.log('[hook]', event.request.url);
	if (event.url.pathname === '/day23/detail') {
		const session = event.cookies.get('session');
		if (!session) {
			error(401, 'Unauthorized');
		}
	}

	return await resolve(event);
};

也就是說當我們要進入 /day23/detail 發現我們在 cookies 裡沒取得到 session 則回傳error(401, 'Unauthorized')

然後來接上登入 API

<!--  day23/+page.svelte -->
<script lang="ts">
	import { goto } from '$app/navigation';

	let email = $state('');
	let password = $state('');
	let response: Response | undefined = $state();
	const login = async () => {
		response = await fetch('/day23/auth', {
			method: 'POST',
			headers: {
				'Content-Type': 'application/json'
			},
			body: JSON.stringify({ email, password })
		});

		if (response.ok) {
			goto('/day23/detail');
		}
	};
</script>

<div class="flex flex-col mx-auto items-center">
	<h1 class="text-primary">Login</h1>

	<div class="w-1/2">
		<label class="form-control w-full">
			<div class="label">
				<span class="label-text">Email</span>
			</div>
			<input name="email" type="text" class="input input-bordered w-full" bind:value={email} />
		</label>
		<label class="form-control w-full">
			<div class="label">
				<span class="label-text">Password</span>
			</div>
			<input
				name="password"
				type="password"
				class="input input-bordered w-full"
				bind:value={password}
			/>
		</label>
		<div class="flex flex-col">
			<button class=" btn btn-primary mt-12 w-1/2 mx-auto" type="button" onclick={login}
				>Login</button
			>
			<button class="btn btn-secondary mt-4 w-1/2 mx-auto" type="button"> Register </button>
		</div>
	</div>
</div>

<div class="toast toast-center">
	{#if response && !response.ok}
		<div class="alert alert-error">
			<span> login failed </span>
		</div>
	{/if}
</div>

這邊唯一比較需要說明的是有使用到 goto 這個 function ,它是 SvelteKit 裡進行 redirect 其中一個方法。

然後我們就能來實測我們的機制是否正常運作了

會發現我們如果在沒有成功登入的狀況下直接改網址到 /day23/detail 就會跳出錯誤,然後登入成功後就會自動到 /day23/detail


參考資料

source code

demo 站點

https://30days-for-svelte5.pages.dev/


上一篇
[Svelte 的奇妙冒險] Day 22 - Link option
下一篇
[Svelte 的奇妙冒險] Day 24 - Snapshot
系列文
Svelte 的奇妙冒險30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言